package lumis.doui.table.field;

import lumis.portal.stability.StableMinor;
import lumis.util.query.*;

import java.util.ArrayList;
import java.util.List;

/**
 * Used to define the mapping between source fields and the database
 *
 * @version $Revision: 13082 $ $Date: 2011-05-27 16:03:12 -0300 (Fri, 27 May 2011) $
 * @since 4.0.4
 */
@StableMinor(version = "6.0", sinceVersion = "4.0")
public class TableSourceFieldMapping
{
	private List<QueryTable> tables = new ArrayList<QueryTable>();
	// TODO: use QueryJoin instead. First QueryAdapter must be corrected how it applies joins
	private List<QueryFilter> joins = new ArrayList<QueryFilter>(); 
	private List<QueryField> fieldsToSelect = new ArrayList<QueryField>();
	private IQueryFilter filter;
	
	/**
	 * Adds a table to this mapping, including a join filter with it. The filter will set the
	 * joinTableColumn on the left and the newTableColumn on the right.
	 * @param newTableName the name of the table to be added.
	 * @param newTableColumn the column of the new table that will be used for the join filter.
	 * @param joinTable the table with whom the new table will be joined with.
	 * @param joinTableColumn the column of <code>joinTable</code> that will be used for the join filter.
	 * @return the new table instance, already added to this mapping.
	 * @since 4.0.4
	 */
	public QueryTable addTable(String newTableName, String newTableColumn, QueryTable joinTable, String joinTableColumn)
	{
		// create the table
		QueryTable newTable = new QueryTable();
		newTable.setTableName(newTableName);
		
		// create the join
		QueryField newTableField = newTable.getField(newTableColumn);
		QueryField joinTableField = joinTable.getField(joinTableColumn);
		QueryFilter join = new QueryFilter();
		join.setFilterConcatenation(IQueryFilter.FILTER_CONCATENATION_AND);
		join.setLeftField(joinTableField);
		join.setFilterOperator(IQueryFilter.FILTER_OPERATOR_EQUAL);
		join.setRightField(newTableField);
		
		// add table and join to this mapping
		getTables().add(newTable);
		getJoins().add(join);
		
		// return the table		
		return newTable;
	}
	
	/**
	 * Adds a field in this mapping.
	 * @param field the field to add.
	 * @since 4.0.4
	 */
	public void addField(QueryField field)
	{
		getFieldsToSelect().add(field);
	}
	
	public void applyMapping(QuerySelect querySelect)
	{
		// add tables and their joins
		for (QueryTable table: getTables())
			applyTable(querySelect, table);
		
		// add fields
		for (QueryField field: getFieldsToSelect())
			applyField(querySelect, field);

		// apply filter
		if (filter != null)
			applyFilter(querySelect, filter);
	}

	/**
	 * Applies a filter to the given query.
	 * @param querySelect the select query.
	 * @param filter the filter to be applied.
	 * @since 4.2.0
	 */
	protected void applyFilter(QuerySelect querySelect, IQueryFilter filter)
	{
		querySelect.addFilter(filter);
	}

	/**
	 * Adds a field to be returned by the given query.
	 * @param querySelect the select query.
	 * @param field the field.
	 * @since 4.2.0
	 */
	protected void applyField(QuerySelect querySelect, QueryField field)
	{
		querySelect.addField(field);
	}

	/**
	 * Includes a table in a query. Must also verifies if the table should or 
	 * should not be included. If it should not be included (e.g. it represents 
	 * a table that is already in the query), this method must return without
	 * performing any change. 
	 * @param querySelect the select query.
	 * @param table the table.
	 * @since 4.2.0
	 */
	protected void applyTable(QuerySelect querySelect, QueryTable table)
	{
		String relationId = table.getRelationId();
		QueryTable addedTable = null;
		if (relationId != null)
			addedTable = querySelect.getTableByRelationId(relationId);
		if (addedTable == null)
		{
			// add the table
			querySelect.addTable(table);
			
			applyTableJoins(querySelect, table);
		}
	}

	/**
	 * Includes in a query the joins of a given table.
	 * <p>
	 * This method is called by {@link #applyTable(QuerySelect, QueryTable)} 
	 * after the table is included in the query.
	 * @param querySelect the select query.
	 * @param table the table whose joins are to be included.
	 * @since 4.2.0
	 */
	protected void applyTableJoins(QuerySelect querySelect, QueryTable table)
	{
		for (QueryFilter join: getJoins())
		{
			// joins considered for the table are the ones where a table's field 
			// is at the right of the filter object
			if (((QueryField)join.getRightField()).getTableOrView() == table)
				querySelect.addFilter(join);
		}
	}
	
	public List<QueryField> getFieldsToSelect()
	{
		return fieldsToSelect;
	}
	public IQueryFilter getFilter()
	{
		return filter;
	}
	public void setFilter(IQueryFilter filter)
	{
		this.filter = filter;
	}
	public List<QueryFilter> getJoins()
	{
		return joins;
	}
	public List<QueryTable> getTables()
	{
		return tables;
	}	
}
